//by thakis

#include <string>
#include <cassert>
#include <vector>
using namespace std;

#include <windows.h>

#include "gcvid.h"

HWND g_hWnd = NULL;
HWAVEOUT g_hWaveOut = NULL;
VideoFile* g_currVideo = NULL;
WAVEFORMATEX g_waveFormat =
{
  WAVE_FORMAT_PCM,
  2, //always stereo output
  0, 0, 0, //numSamples, avgBytesPerSec, nBlockAlign filled in later
  16, //always 16 bpp
  0 //zero extra bytes
};

struct Frame
{
  vector<s16> buffer;
  VideoFrame frame;
  WAVEHDR header;
};

//more cached frames give faster playback but
//more ram usage
vector<Frame> g_frames(10);
int g_currentFrame = 0;

bool g_done = false;


void doFrameLoad(int where, WAVEHDR* hd = NULL)
{
  //load video
  g_currVideo->getCurrentFrame(g_frames[where].frame);

  //load audio
  if(hd == NULL)
    g_frames[where].header.dwBufferLength = g_currVideo->getCurrentBuffer(&g_frames[where].buffer[0])*2*2;
  else
  {
    hd->dwBufferLength = g_currVideo->getCurrentBuffer((s16*)hd->lpData)*2*2;
  }

  //windows wants r and b channels swapped...do that:
  //NOTE: This is not neccessary anymore because I changed
  //jpeglib to output images with rgb already swapped
  //(by changing the RGB_RED and RGB_BLUE defines in
  //jmorecfg.h), so you need this patched jpeglib if
  //you want to leave out the following function (and
  //it's a costy function)
  //swapRB(g_frames[where].frame);
}

bool openFile(const string& fileName)
{
  closeVideo(g_currVideo);
  g_currVideo = openVideo(fileName);
  if(g_currVideo != NULL)
  {
    g_currentFrame = 0;

    for(int i = 0; i < g_frames.size(); ++i)
      g_frames[i].buffer.resize(g_currVideo->getMaxAudioSamples()*2);

    return true;
  }
  else
    return false;
}

void closeAudio()
{
  if(g_hWaveOut != NULL)
  {
    waveOutReset(g_hWaveOut);
    for(int i = 0; i < g_frames.size(); ++i)
    {
      waveOutUnprepareHeader(g_hWaveOut, &g_frames[i].header, sizeof(WAVEHDR));
      g_frames[i].buffer.clear();
    }
    g_hWaveOut = NULL;
  }
}

void loadVideo(const string& fileName)
{
  //MessageBox(g_hWnd, fileName.c_str(), "", MB_OK);
  KillTimer(g_hWnd, 0);

  closeAudio();

  if(!openFile(fileName))
    return;

  //make window as large as video
  RECT rNew = { 0, 0, g_currVideo->getWidth(), g_currVideo->getHeight() }, rCurr;
  GetWindowRect(g_hWnd, &rCurr);
  AdjustWindowRect(&rNew, WS_OVERLAPPEDWINDOW, FALSE);
  MoveWindow(g_hWnd, rCurr.left, rCurr.top,
    rNew.right - rNew.left, rNew.bottom - rNew.top,
    FALSE); //we InvalidateRect() ourselves - later

  g_currVideo->loadNextFrame();

  bool playSound = false;
  if(g_currVideo->hasSound())
  {
    //animaton is controlled by WM_WOM_DONE messages

    //open sound device
    g_waveFormat.nSamplesPerSec = g_currVideo->getFrequency();
    g_waveFormat.nBlockAlign = (g_waveFormat.wBitsPerSample/8)*g_waveFormat.nChannels;
    g_waveFormat.nAvgBytesPerSec = g_waveFormat.nBlockAlign*g_waveFormat.nSamplesPerSec;

    if(waveOutOpen(&g_hWaveOut, WAVE_MAPPER, &g_waveFormat, (DWORD)g_hWnd,
      0, CALLBACK_WINDOW) == MMSYSERR_NOERROR)
    {
      playSound = true;

      for(int i = 0; i < g_frames.size(); ++i)
      {
        g_frames[i].header.lpData = (LPSTR)&g_frames[i].buffer[0];
        g_frames[i].header.dwBufferLength = g_frames[i].buffer.size()*2*2;
        g_frames[i].header.dwBytesRecorded = 0;
        g_frames[i].header.dwUser = 0;
        g_frames[i].header.dwFlags = 0;
        g_frames[i].header.dwLoops = 0;
        g_frames[i].header.lpNext = NULL;
        g_frames[i].header.reserved = 0;
        waveOutPrepareHeader(g_hWaveOut, &g_frames[i].header, sizeof(WAVEHDR));
      }

      doFrameLoad(0);

      //cache frames:
      for(int k = 1; k < g_frames.size(); ++k)
      {
        g_currVideo->loadNextFrame();
        doFrameLoad(k);
      }

      for(int j = 0; j < g_frames.size(); ++j)
        waveOutWrite(g_hWaveOut, &g_frames[j].header, sizeof(WAVEHDR));
    }
  }
  
  if(!playSound)
  {
    doFrameLoad(0);
    if(g_currVideo->getFps() != 0.f && g_currVideo->getFrameCount() > 1)
    {
      //animation is controlled by WM_TIMER messages
      SetTimer(g_hWnd, 0, (UINT)(1000/g_currVideo->getFps() + .5f), NULL);
    }
  }

  InvalidateRect(g_hWnd, NULL, FALSE);
}

int getNextFrameCacheNr(int now)
{
  ++now;
  if(now >= g_frames.size())
    now = 0;
  return now;
}

void nextFrame(WAVEHDR* hd = NULL)
{
  if(g_hWaveOut != NULL)
    g_currentFrame = getNextFrameCacheNr(g_currentFrame);

  //update title bar
  float fps = g_currVideo->getFps();
  float currTime = 0.f, totalTime = 0.f;
  int currFrameNr = g_currVideo->getCurrentFrameNr() + 1;
  if(fps != 0.f)
  {
    currTime = currFrameNr/fps;
    totalTime = g_currVideo->getFrameCount()/fps;
  }

  char buff[320];
  sprintf(buff, "thpPlay 1.5 %d/%d frames %f/%f seconds",
    currFrameNr, g_currVideo->getFrameCount(), currTime, totalTime);
  SetWindowText(g_hWnd, buff);


  //send out prepared data
  InvalidateRect(g_hWnd, NULL, FALSE);
  UpdateWindow(g_hWnd);

  //load next frame
  int nextFrame = 0;
  
  if(g_hWaveOut != NULL)
    nextFrame = getNextFrameCacheNr(g_currentFrame);
  g_currVideo->loadNextFrame();
  doFrameLoad(nextFrame, hd);

  //enqueue next audio frame
  if(g_hWaveOut != NULL)
    waveOutWrite(g_hWaveOut, hd, sizeof(WAVEHDR));

  //process messages so that the window still reacts even
  //if it's busy processing MM_WOM_DONE messages:
  //(doesn't work??? -> because this loop produces only
  //MM_WOM_DONE messages as well -> use a callback
  //instead of a mesage for audio?)
  /*
  MSG msg;
  while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) && !g_done)
  {
    if(msg.message == WM_QUIT)
    {
      closeAudio(); //stop MM_WOM_DONE messages
      g_done = true;
    }

    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }
  */
}

LRESULT CALLBACK eventListener(HWND hWnd, UINT msg, WPARAM wP, LPARAM lP)
{
  switch(msg)
  {
    case WM_PAINT:
    {
      PAINTSTRUCT ps;
      HDC hDc = BeginPaint(hWnd, &ps);

      if(g_currVideo != NULL)
      {
        const VideoFrame& currFrame = g_frames[g_currentFrame].frame;

        BITMAPINFO bi;
        ZeroMemory(&bi, sizeof(bi));
        bi.bmiHeader.biSize = sizeof(bi);
        bi.bmiHeader.biWidth = currFrame.getWidth();
        bi.bmiHeader.biHeight = currFrame.getHeight();
        bi.bmiHeader.biPlanes = 1;
        bi.bmiHeader.biBitCount = 24;

        SetDIBitsToDevice(hDc, 0, 0, currFrame.getWidth(), currFrame.getHeight(),
          0, 0, 0, currFrame.getHeight(), currFrame.getData(), &bi, DIB_RGB_COLORS);
      }

      EndPaint(hWnd, &ps);
      return 0;
    }break;

    case WM_DROPFILES:
    {
      HDROP hDrop = (HDROP)wP;
      char buff[1024];
      DragQueryFile(hDrop, 0, buff, 1024);

      loadVideo(buff);

      DragFinish(hDrop);
      return 0;
    }break;

    case MM_WOM_DONE:
    {
      nextFrame((WAVEHDR*)lP);
      return 0;
    };

    case WM_TIMER:
    {
      nextFrame();
      return 0;
    }break;

    case WM_DESTROY:
    {
      closeVideo(g_currVideo);
      closeAudio();
      PostQuitMessage(0);
      return 0;
    }break;

    default:
      return DefWindowProc(hWnd, msg, wP, lP);
  }
}

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE old, LPSTR params, int showCom)
{
  WNDCLASSEX wc = { sizeof(wc), CS_HREDRAW | CS_VREDRAW, eventListener, 0, 0, hInst,
                    LoadIcon(NULL, IDI_WINLOGO), LoadCursor(NULL, IDC_ARROW),
                    (HBRUSH)GetStockObject(WHITE_BRUSH), NULL, "class",
                    LoadIcon(NULL, IDI_WINLOGO)
                  };

  if(!RegisterClassEx(&wc))
    return -1;

  HWND hWnd = CreateWindow("class", "thpplay 1.5", WS_OVERLAPPEDWINDOW,
                             CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
                             NULL, NULL, hInst, NULL);

  if(hWnd == NULL)
    return -1;
  g_hWnd = hWnd;

  if(params[0] != '\0')
  {
    //load image, strip quotes windows explorer puts around filename
    if(params[0] == '\"')
    {
      params = params + 1;
      *strstr(params, "\"") = '\0';
    }
    loadVideo(params);
  }

  
  ShowWindow(hWnd, showCom);

  DragAcceptFiles(hWnd, TRUE);

  MSG msg;
  while(GetMessage(&msg, NULL, 0, 0) && !g_done)
  {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }
  g_done = true;

  return msg.wParam;
}

#if 0
float now = GetTickCount()/1000.f;
      g_totalTime += now - g_startTime;
      g_startTime = now;
      if(g_totalTime >= totalTime || fps == 0.f)
      {
        g_totalTime = 0;

        while(g_currVideo->getCurrentFrameNr() != 0)
        {
          g_currVideo->loadNextFrame();
          //windows wants r and b channels swapped...do that
          swapRB(g_currVideo->getCurrentFrame());

          currTime = (g_currVideo->getCurrentFrameNr() + 1)/fps;
        }
      }
      else
      {

        while(currTime <= g_totalTime)
        {
          g_currVideo->loadNextFrame();
          //windows wants r and b channels swapped...do that
          swapRB(g_currVideo->getCurrentFrame());

          currTime = (g_currVideo->getCurrentFrameNr() + 1)/fps;
        }
      }
#endif
